package com.yifeng.repo.base.utils.redis.worker;

import com.yifeng.repo.base.utils.redis.client.AbstractJedisCommand;
import com.yifeng.repo.base.utils.redis.client.JedisClient;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.SetParams;

/**
 * Created by daibing on 2022/8/9.
 */
public class RedisLockWorker {
    private static final String LOCK_SUCCESS = "OK";
    private final JedisClient jedisClient;

    public RedisLockWorker(JedisClient jedisClient) {
        this.jedisClient = jedisClient;
    }

    public boolean lock(String key, String value, long expireMillis) {
        SetParams params = SetParams.setParams().nx().px(expireMillis);
        String lockResult = new AbstractJedisCommand<String>() {
            @Override
            protected String doRun(Jedis jedis) {
                return jedis.set(key, value, params);
            }
        }.doExecute(jedisClient);
        return LOCK_SUCCESS.equals(lockResult);
    }

    public boolean unlock(String key, String value) {
        // 如果通过key获取的数据与第一个入参相等，则将该key删除，并且返回1，否则返回0
        String lua = "if redis.call(\"get\", KEYS[1]) == ARGV[1] then "
                + "    return redis.call(\"del\", KEYS[1]) "
                + "else "
                + "    return 0 "
                + "end";
        Object unlockResult = new AbstractJedisCommand<Object>() {
            @Override
            protected Object doRun(Jedis jedis) {
                return jedis.eval(lua, 1, key, value);
            }
        }.doExecute(jedisClient);
        return Long.valueOf(1) == unlockResult;
    }

    /**
     * 检查获取当前key的值：
     * 1、如果是空或者大于等于回拨边界值，就设置为0并且设置ttl，注意正常set返回的是OK，所以加上return 0
     * 2、否则就递增
     *
     * @param key           key
     * @param round         回拨边界值
     * @param expireSeconds 过期时间
     * @return 操作后key对应的值
     */
    public long incr(String key, long round, long expireSeconds) {
        String lua = "local cnt = tonumber(redis.call(\"get\", KEYS[1]))"
                + "if cnt == nil or cnt >= tonumber(ARGV[1]) then"
                + "    redis.call(\"set\", KEYS[1], 0, 'EX', ARGV[2]) "
                + "    return 0 "
                + "else "
                + "    return redis.call(\"incr\", KEYS[1]) "
                + "end";
        Object count = new AbstractJedisCommand<Object>() {
            @Override
            protected Object doRun(Jedis jedis) {
                return jedis.eval(lua, 1, key, String.valueOf(round), String.valueOf(expireSeconds));
            }
        }.doExecute(jedisClient);
        return Integer.parseInt(String.valueOf(count));
    }

    /**
     * 门店编码递增核心逻辑
     * 1、检查key对应的value：不存在，或者不是有效值（4位，第1位[D-Z]，第2位[A-Z]，第3位[0-9]，第4位[0-9]）直接返回无效值-1
     * 2、对key对应value做递增操作：达到边界值就要做升位
     * 3、边界值ZZ99继续获取数据越界，暂时也返回-1
     */
    public String codeIncr(String key, long expireSeconds) {
        String lua = "local value = redis.call('get', KEYS[1]) " +
                "if value == false then " +
                "    return -1 " +
                "end " +
                "if string.len(value) ~= 4 then " +
                "    return -1 " +
                "end " +
                "local storeCode = nil " +
                "local part4 = string.byte(value, 4) " +
                "if part4 == string.byte('9') then  " +
                "    local part3 = string.byte(value, 3) " +
                "    if part3 == string.byte('9') then " +
                "        local part2 = string.byte(value, 2) " +
                "        if part2 == string.byte('Z') then " +
                "            local part1 = string.byte(value, 1) " +
                "            if part1 == string.byte('Z') then " +
                "                return -1 " +
                "            else " +
                "                storeCode = string.char(part1 + 1)..'A00' " +
                "            end " +
                "        else " +
                "            storeCode = string.sub(value, 1, 1)..string.char(part2 + 1)..'00' " +
                "        end " +
                "    else " +
                "        storeCode = string.sub(value, 1, 2)..string.char(part3 + 1)..'0' " +
                "    end " +
                "else " +
                "    storeCode = string.sub(value, 1, 3)..string.char(part4 + 1) " +
                "end " +
                "redis.call('set', KEYS[1], storeCode, 'EX', ARGV[1])" +
                "return storeCode";
        Object code = new AbstractJedisCommand<Object>() {
            @Override
            protected Object doRun(Jedis jedis) {
                return jedis.eval(lua, 1, key, String.valueOf(expireSeconds));
            }
        }.doExecute(jedisClient);
        return String.valueOf(code);
    }

}
